!function () {
    var _exit = d3._exit;
    if (typeof (d3) === "undefined") {
        d3 = {};
        _exit = true;
    }
    d3.helper = {};

    d3.helper.tooltip = function () {
        if (_exit)
            return;
        function tooltip(selection) {

            selection
                    .on('mouseover.tooltip', function (d, p1, p2) {
                        if (!d.ballonHtml)
                            return;
                        var leftPlus = 15;
                        var topPlus = 0;
                        var b = document.createElement("div");
                        $(b)
                                .addClass("gw-balloon")
                                .html(d.ballonHtml)
                                .zIndex(1001)
                                .appendTo($(document.body))
                                .css("border-color", d.color)
                                .position({my: "left+" + leftPlus + " top", of: d3.event, collision: "fit"});
                        $(this).mousemove(function (e) {
                            $(b).position({my: "left+" + leftPlus + " top", of: e, collision: "fit"});
                        });
                        $(this).mouseleave(function () {
                            $(b).remove();
                        });
                    });

        }

        tooltip.attr = function (_x) {
            if (!arguments.length)
                return attrs;
            attrs = _x;
            return this;
        };

        tooltip.style = function (_x) {
            if (!arguments.length)
                return styles;
            styles = _x;
            return this;
        };

        return tooltip;
    };

    var gwSunburst = {
        version: "0.0.1"
    };
    gwSunburst.padding = 5;
    gwSunburst.duration = 1000;
    if (!_exit) {
        gwSunburst.x = d3.scale.linear().range([0, 2 * Math.PI]);
        gwSunburst.color = d3.scale.category20c();
        gwSunburst.partition = d3.layout.partition()
                .sort(function (a, b) {
                    return d3.descending(a.value, b.value);
                })
                .value(function (d) {
                    return d.value;
                });
        gwSunburst.arc = d3.svg.arc()
                .startAngle(function (d) {
                    var r = Math.max(0, Math.min(2 * Math.PI, gwSunburst.x(d.x)));
                    return isNaN(r) ? 1 : r;
                })
                .endAngle(function (d) {
                    var r = Math.max(0, Math.min(2 * Math.PI, gwSunburst.x(d.x + d.dx)));
                    return isNaN(r) ? 1 : r;
                })
                .innerRadius(function (d) {
                    return getInnerRadius(d);
                })
                .outerRadius(function (d) {
                    return getOuterRadius(d);
                });
    }

    var htmlWait = "<div class='gw-wait' style='height:100%;width:100%;background:white;display:table;border-radius:6px;'>"
            + "<span style='display:table-cell;text-align:center;vertical-align:middle;'>"
            + "<i class='fa fa-cog fa-5x fa-spin'></i></span></div>";

    function getInnerRadius(d) {
        if (d.depth < d.centerDepth) {
            return 0;
        } else if (d.depth === d.centerDepth) {
            return 0;
        } else {
            var layerCount = d.maxDepth - d.centerDepth + 1;
            var result = (2 * (d.depth - d.centerDepth) - 1) * (d.radius / (2 * layerCount - 1));
            return result;
        }
    }

    function getOuterRadius(d) {
        if (d.depth < d.centerDepth) {
            return 0;
        } else if (d.depth === d.centerDepth && !d.isCenterNode) {
            return 0;
        } else {
            var layerCount = d.maxDepth - d.centerDepth + 1;
            var branchMaxDepth = getBranchMaxDepth(d);
            if (branchMaxDepth === d.depth) {
                var result = (2 * (d.maxDepth - d.centerDepth) + 1) * (d.radius / (2 * layerCount - 1));
                return result;
            } else {
                var result = (2 * (d.depth - d.centerDepth) + 1) * (d.radius / (2 * layerCount - 1));
                return result;
            }
        }
    }

    function getBranchMaxDepth(d) {
        if (d.branchMaxDepth)
            return d.branchMaxDepth;
        var result = d.depth;
        if (d.children) {
            for (var i = 0; i < d.children.length; i++) {
                var childMaxDepth = getBranchMaxDepth(d.children[i]);
                if (result < childMaxDepth) {
                    result = childMaxDepth;
                }
            }
        }
        d.branchMaxDepth = result;
        return d.branchMaxDepth;
    }

    function getColor(d) {
        if (d.color)
            return d.color;
        switch (d.depth) {
            case 0:
                return "transparent";
            case 1:
                d.color = gwSunburst.color(d.code);
                if (d.selected) {
                    d.color = d3.rgb(d.color).darker();
                }
                return d.color;
            default:
                if (d.parent) {
                    return getColor(d.parent);
                }
        }
        return "black";
    }

    function getOpacity(d) {
        if (d.opacity)
            return d.opacity;
        else if (d.selected === 1) {// || d.depth === 1
            d.opacity = 1;
            return d.opacity;
        }
        var o = 0;
        switch (d.depth) {
            case 1:
                o = 0.8;
                break;
            case 2:
                o = 0.7;
                break;
            case 3:
                o = 0.6;
                break;
            case 4:
                o = 0.5;
                break;
            case 5:
                o = 0.4;
                break;
            case 6:
                o = 0.3;
                break;
        }
        var v = ((d.value || 0) === 0) ? 1 : d.value;
        var pv = (d.parent ? (((d.parent.value || 0) === 0) ? 1 : d.parent.value) : 1);
        o = o - o * v / pv;
        d.opacity = Math.max(o, .3);
        return d.opacity;
    }

    gwSunburst.refreshSunBurstGraph = refreshSunBurstGraph;
    var graphs = [];
    function getGraph(divId) {
        var index = -1;
        for (var i = 0; i < graphs.length; i++) {
            if (graphs[i].divId === divId) {
                index = i;
                break;
            }
        }
        if (index > -1) {
            return graphs[index];
        } else {
            var graph = {};
            graph.divId = divId;
            graphs.push(graph);
            return graph;
        }
    }

    function getDefaultLegendHtml(d) {
        return d.value;
    }

    var clsTitle = ".gw-graph-layout-title",
            clsLegend = ".gew-graph-layout-legend";

    function refreshSunBurstGraph(divId, jsonPath, config) {
        if (_exit)
            return;
        var graph = getGraph(divId);
        graph.balloonFunction = config.balloonFunction || getDefaultLegendHtml;

        var p = $("#" + divId).parent();
        p.children(".gw-graph-layout-chart").hide();
        p.children(".gw-graph-layout-title").hide();
        var divWait = p.children(".gw-wait");
        if (divWait.length === 0) {
            p.append(htmlWait);
            divWait = p.children(".gw-wait");
        }
        divWait.show();

        $.ajax({
            dataType: "json",
            url: jsonPath,
            success: function (json) {
                divWait.hide();
                p.children(".gw-graph-layout-title").show();
                p.children(".gw-graph-layout-chart").show();

                var div = d3.select("#" + divId);

                if (json.title) {
                    $("#" + divId).parent().find(clsTitle).html(json.title);
                }

                var graph = getGraph(divId);
                div.selectAll("svg").remove();
                div.selectAll("div").remove();

                var balloonTemplate = json.balloonTemplate;

                graph.h = $("#" + divId).parent().height() - $("#" + divId).parent().find(clsTitle).height() - $("#" + divId).parent().find(clsLegend).height() - 2 * gwSunburst.padding;
                graph.w = $("#" + divId).parent().width() - 2 * gwSunburst.padding;
                graph.r = Math.min(graph.w, graph.h) / 2;

                graph.vis = div
                        .append("svg")
                        .attr("width", graph.r * 2)
                        .attr("height", graph.r * 2)
                        .append("g")
                        .attr("transform", "translate(" + [graph.r, graph.r] + ")");

                if (json.data.length === 0) {
                    return;
                }
                var nodes = gwSunburst.partition
                        .nodes({children: json.data[0].children})
                        .filter(function (d) {
                            return d.value > 0;
                        });
                if (nodes.length === 0)
                    return;
                nodes[0].name = json.data[0].name;
                nodes[0].code = json.data[0].code;
                nodes[0].selected = json.data[0].selected;
                graph.centerNode = nodes[0];
                graph.centerNode.isCenterNode = true;
                var totalParent = graph.centerNode;
                while (totalParent.parent)
                    totalParent = totalParent.parent;
                setNodeCenterDepth(totalParent, graph.centerNode.depth);

                var maxDepth = graph.centerNode.depth;
                for (var i = 0; i < nodes.length; i++) {
                    if (nodes[i].depth > maxDepth)
                        maxDepth = nodes[i].depth;
                }
                nodes.forEach(function (node) {
                    node.radius = graph.r;
                    node.centerDepth = graph.centerNode.depth;
                    node.maxDepth = maxDepth;
                    node.ballonHtml = graph.balloonFunction(balloonTemplate, node);
                    node.balloonMaxWidth = config.balloonMaxWidth;
                });
                graph.path = graph.vis.selectAll("path").data(nodes);
                graph.firstSelected = getSelectedNode(graph.centerNode) || graph.centerNode;
                if ((graph.firstSelected.depth === maxDepth || !graph.firstSelected.children) && graph.firstSelected.parent != null) {
                    graph.firstSelected = graph.firstSelected.parent;
                }
                graph.path.enter()
                        .append("path")
                        .attr("id", function (d, i) {
                            return "path-" + i;
                        })
                        .attr("d", gwSunburst.arc)
                        .attr("fill-rule", "evenodd")
                        .style("fill", function (d) {
                            return getColor(d);
                        })
                        .style("opacity", function (d) {
                            return getOpacity(d);
                        })
                        .style("cursor", "pointer")
                        .style("stroke", "#fff")
                        .style("stroke-width", "1")
                        .on("click", onClick)
                        .on("mouseenter", onOver)
                        .on("mouseleave", onLeave)
                        .each(stash)
                        .call(d3.helper.tooltip());
                ;
                if (graph.firstSelected) {
                    onPathClick(graph.firstSelected, graph, true);
                } else {
                    captionOnGraph(graph.centerNode, graph.vis);
                }

                function onClick(d) {
                    onPathClick(d, graph);
                    if (config.onClick){
                        config.onClick(d.url_code);
                    }
                }

                function onOver(d) {
                    onPathOver(d, graph, 0.10);
                }

                function onLeave(d) {
                    onPathLeave(d, graph, 0.10);
                }
            },
            error: function (jqXHR, textStatus, errorThrown) {
                //webix.message(textStatus);
            }
        });
        //});
    }

    function captionOnGraph(graph) {
        return;
        var texts = getTexts(graph.centerNode, 10);
        var text = graph.vis.selectAll("text").data(texts);
        text
                .enter()
                .append("text")
                .style("fill-opacity", 1)
                .style("fill", function (d) {
                    return d.fill;
                })
                .attr("text-anchor", function (d) {
                    return d.textAnchor;
                })
                .attr("dy", ".2em")
                .attr("transform", function (d) {
                    return "rotate(" + d.rotate + ")translate(" + d.translate + ")rotate(" + (d.rotate > 90 ? -180 : 0) + ")";
                })
                .on("click", onTextClick)
                .text(function (d) {
                    return d.text;
                });
        function onTextClick(d) {
            onPathClick(d.node, graph);
        }
    }

    function getSelectedNode(node) {
        var result = null;
        if (node.selected) {
            result = node;
            if (node.children) {
                for (var i = 0; i < node.children.length; i++) {
                    var r = null;
                    r = getSelectedNode(node.children[i]);
                    if (r) {
                        result = r;
                        break;
                    }
                }
            }
        }
        return result;
    }

    function setNodeCenterDepth(d, centerNodeDepth) {
        d.centerDepth = centerNodeDepth;
        if (d.children) {
            for (var i = 0; i < d.children.length; i++) {
                setNodeCenterDepth(d.children[i], centerNodeDepth);
            }
        }
    }

    function onPathClick(d, graph, mE) {
        if (graph.centerNode === d) {
            if (d.parent) {
                onPathClick(d.parent, graph, true);
                return;
            }
        }
        if (!mE && graph.clickInProgress) {
            return;
        } else {
            graph.zoomInProgress = false;
            graph.clickInProgress = true;
            graph.centerNode.isCenterNode = false;
            graph.centerNode = d;
            graph.centerNode.isCenterNode = true;
            var totalParent = graph.centerNode;
            while (totalParent.parent)
                totalParent = totalParent.parent;
            setNodeCenterDepth(totalParent, graph.centerNode.depth);
        }
        var transitions = 0;
        graph.path
                .transition()
                .duration(gwSunburst.duration)
                .attrTween("d", arcTween(d))
                .each("start", function () {
                    transitions++;
                })
                .each("end", function () {
                    if (--transitions === 0) {
                        onPathClickCompleted(graph);
                    }
                    ;
                })
                ;
        graph.vis.selectAll("text").remove();
    }

    function onPathClickCompleted(graph) {
        graph.clickInProgress = false;
        captionOnGraph(graph);
    }

    function onPathLeave(d, graph, k) {
        if (graph.clickInProgress || graph.zoomInProgress)
            return;
        if (event.relatedTarget !== null && (typeof event.relateTarget != undefined)) {
            if (event.relatedTarget.tagName === "path") {
                var newD = event.relatedTarget.__data__;
                if (newD != null && (typeof newD != undefined)) {
                    if (newD == graph.centerNode) {
                        var node = d;
                        for (var depth = d.depth; depth > graph.centerNode.depth; depth--) {
                            onPathOver(node, graph, 0, true);
                            node = node.parent;
                        }
                    } else {
                        onPathOver(newD, graph, k);
                    }
                }
            } else {
                var node = d;
                for (var depth = d.depth; depth > graph.centerNode.depth; depth--) {
                    onPathOver(node, graph, 0, true);
                    node = node.parent;
                }
            }
        }
        graph.vis.selectAll("text").remove();
    }

    function onPathOver(d, graph, k, mE) {
        if (!mE && (graph.zoomInProgress || graph.clickInProgress || graph.centerNode.depth >= d.depth)) {
            return;
        } else {
            graph.zoomedNode = d;
            graph.zoomInProgress = true;
        }
        var parent = d.parent;
        if (parent) {
            if (k === 0) {
                var x = parent.x;
                parent.children
                        .forEach(function (node) {
                            var k4 = parent.dx / node.parent.value;
                            x += reposition(node, x, isNaN(k4) ? 0 : k4);
                        });
            } else {
                var zoomLimit = 3;
                var selectedIndex = parent.children.indexOf(d);
                var zoomCount = 0;
                parent.children.forEach(function (node) {
                    node.deltaIndex = Math.abs(parent.children.indexOf(node) - selectedIndex);
                    if (node.deltaIndex < zoomLimit) {
                        zoomCount++;
                    }
                });

                d.k3 = Math.max(k, d.value / d.parent.value);
                var zoomedK3 = d.k3;
                var zoomedValue = d.value;
                if (zoomCount === parent.children.length) {
                    k = 1;
                }
                for (var di = 1; di < zoomLimit; di++) {
                    var k3 = 0;
                    for (var i = 0; i < parent.children.length; i++) {
                        var node = parent.children[i];
                        if (node.deltaIndex === di) {
                            node.k3 = Math.max(((1 - zoomedK3) * k) / 2, Math.min(((1 - zoomedK3) / 2 - k3), node.value / node.parent.value));
                            k3 += node.k3;
                            zoomedValue += node.value;
                        }
                        if (i === parent.children.length - 1)
                            zoomedK3 += k3;
                    }
                }
                if (zoomCount === parent.children.length) {
                    for (var i = 0; i < parent.children.length; i++) {
                        var node = parent.children[i];
                        if (node.deltaIndex < zoomLimit) {
                            node.k3 = node.k3 / zoomedK3;
                        }
                    }
                } else {
                    for (var i = 0; i < parent.children.length; i++) {
                        var node = parent.children[i];
                        if (node.deltaIndex >= zoomLimit) {
                            node.k3 = node.value / (node.parent.value - zoomedValue) * (1 - zoomedK3);
                        }
                    }
                }

                var x = parent.x;
                parent.children
                        .forEach(function (node) {
                            var k4 = parent.dx * node.k3 / node.value;
                            x += reposition(node, x, isNaN(k4) ? 0 : k4);
                        });
            }
        } else {
            reposition(d, 0, d.dx / d.value);
        }
        var transitions = 0;
        graph.path
                .transition()
                .duration(gwSunburst.duration)
                .attrTween("d", arcTween2)
                .each("start", function () {
                    transitions++;
                })
                .each("end", function () {
                    if (--transitions === 0) {
                        onPathOverCompleted(graph);
                    }
                    ;
                });
        graph.vis.selectAll("text").remove();
    }

    function onPathOverCompleted(graph) {
        graph.zoomInProgress = false;
        captionOnGraph(graph);
    }

    function arcTween(d) {
        var my = maxY(d),
                y = d3.scale.sqrt().range([0, d.radius]),
                //y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, d.radius]),
                xd = d3.interpolate(gwSunburst.x.domain(), [d.x, d.x + d.dx]),
                yd = d3.interpolate(y.domain(), [d.y, my]),
                yr = d3.interpolate(y.range(), [d.y ? 5 : 0, d.radius]);
        return function (d) {
            return function (t) {
                gwSunburst.x.domain(xd(t));
                y.domain(yd(t)).range(yr(t));
                return gwSunburst.arc(d);
            };
        };
    }

// Recursively reposition the node at position x with scale k.
    function reposition(node, x, k) {
        node.x = x;
        if (node.children && (n = node.children.length)) {
            var i = -1, n;
            while (++i < n)
                x += reposition(node.children[i], x, k);
        }
        var dx = node.value * k;
        if (isNaN(dx)) {
            return node.dx = 0;
        }
        return node.dx = node.value * k;
    }

// Stash the old values for transition.
    function stash(d) {
        d.x0 = d.x;
        d.dx0 = d.dx;
    }

// Interpolate the arcs in data space.
    function arcTween2(a) {
        var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
        return function (t) {
            var b = i(t);
            a.x0 = b.x;
            a.dx0 = b.dx;
            return gwSunburst.arc(b);
        };
    }

    function maxY(d) {
        return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
    }

//    function brightness(rgb) {
//        return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
//    }

    function splitToRows(caption, maxCharsPerLine) {
        caption = caption || "";
        maxCharsPerLine = maxCharsPerLine || 20;
        var result = [];
        var words = caption.split(" ");
        var line = "";
        for (var n = 0; n < words.length; n++) {
            var testLine = line + words[n] + " ";
            if (testLine.length > maxCharsPerLine)
            {
                result.push(line);
                line = words[n] + " ";
            }
            else {
                line = testLine;
            }
        }
        if (line != "") {
            result.push(line);
        }
        return result;
    }

    function getTexts(selectedRootNode, hL) {
        var result = [];
        hL = hL || 14;
        result = result.concat(getTextsFromNode(selectedRootNode, hL));
        if (selectedRootNode.children) {
            for (var i = 0; i < selectedRootNode.children.length; i++) {
                result = result.concat(getTexts(selectedRootNode.children[i], hL));
            }
        }
        return result;
    }

    function getTextsFromNode(node, hL) {
        var result = [];
        var fill = "#111"; //brightness(d3.rgb(color((node.children || node.parent || node).id))) < 125 ? "#eee" : "#000";

        var y = d3.scale.sqrt().range([0, node.radius]);
        //var y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, node.radius]);
        var rI = Math.max(0, node.y ? y(node.y) : node.y);
        var rE = Math.max(0, y(node.y + node.dy));
        var rD = rE - rI;
        var maxCharsPerLine = rD / 6;
        var t = splitToRows(node.name || "", maxCharsPerLine);
        var lC = t.length;
        var aS = Math.max(0, Math.min(2 * Math.PI, gwSunburst.x(node.x)));
        var aE = Math.max(0, Math.min(2 * Math.PI, gwSunburst.x(node.x + node.dx)));
        var aD = aE - aS;
        var aL = rI === 0 ? 0 : Math.acos(1 - (hL * hL) / (2 * rI * rI));
        var rC = aL === 0 ? 0 : Math.min(parseInt(aD / aL), lC);
        var tAS = aS + (aD - aL * Math.max(0, (rC - 1))) / 2;
        var tAE = aE - (aD - aL * Math.max(0, (rC - 1))) / 2;
        var translate = (y(node.y) + gwSunburst.padding);
        if (rC === 0 || (rC === 1 && t.length > 1))
            return result;
        for (var j = 0; j < rC; j++) {
            var r = {};
            r.text = (j === rC - 1 && rC !== t.length) ? "..." : t[j];
            r.fill = fill;
            r.translate = translate;
            var rotate = (aS + aD / 2 > Math.PI ? (tAE - j * aL) : (tAS + j * aL)) * 180 / Math.PI - 90;
            rotate = isNaN(rotate) ? 0 : rotate;
            r.rotate = rotate;
            r.textAnchor = rotate + 90 > 180 ? "end" : "start";
            r.node = node;
            result.push(r);
        }
        return result;
    }

    this.gwSunburst = gwSunburst;
}();